package com.tbit.main.service.handler;


import com.tbit.common.utils.IpUtil;
import com.tbit.main.constant.OperateLog;
import com.tbit.main.controller.interceptor.LoginUserContextHolder;
import com.tbit.main.service.AccountService;
import com.tbit.main.service.AccountUserService;
import com.tbit.main.service.TokenService;
import com.tbit.main.service.UserService;
import com.tbit.main.service.manager.factory.AsyncFactory;
import com.tbit.main.service.manager.spring.SpringUtils;
import com.tbit.main.service.manager.util.AsyncManager;
import com.tbit.main.service.manager.util.StringUtils;
import com.tbit.main.util.ReflectUtilsType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author MyWin
 * @version 1.0
 * @createTime 2019-12-07 17:31
 */
@Slf4j
@Aspect
@Component
public class OperateLogAspect {

    @Autowired
    private TokenService tokenService;

    public static void main(String[] args) {
        AsyncManager.me().execute(AsyncFactory.recordOper(null, null, null, null, null, null));
    }

    /**
     * 对标注有operateLog注解的进行拦截,@annotation表示标注了某个注解的所有方法。
     */
    @Pointcut("@annotation(com.tbit.main.constant.OperateLog)")
    public void controllerAspect() {

    }

    /**
     * 环绕
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around("controllerAspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取请求参数的键和值，并记录
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //获取ip地址
        String ip = IpUtil.getClientIp(request);
        final String ipAddress = ip;//ip地址
        final String requestURI = request.getRequestURI();
        //获取目标类
        Object target = joinPoint.getTarget();
        //获取方法名称
        String methodName = joinPoint.getSignature().getName();
        Signature sign = joinPoint.getSignature();
        if (!(sign instanceof MethodSignature)) {
            throw new RuntimeException("该注解只支持方法");
        }
        MethodSignature methodSignature = (MethodSignature) sign;
        //获取方法参数类型
        Class<?>[] methodParamClazz = methodSignature.getMethod().getParameterTypes();
        String[] params = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        Map<String, Object> nameValueMap = put(methodParamClazz, params, args);

        //获取目标方法
        Method method = target.getClass().getMethod(methodName, methodParamClazz);
        Object obj = null;
        if (null != method) {
            //如果目标方法中有注解OperateLog
            if (method.isAnnotationPresent(OperateLog.class)) {
                //获取日志类
                final OperateLog annotation = method.getAnnotation(OperateLog.class);
                //先放行
                obj = joinPoint.proceed();
                /*异步写入日志*/
                try {
                    AsyncManager.me().execute(AsyncFactory.recordOper(LoginUserContextHolder.getLoginUser(), annotation, nameValueMap, obj, ipAddress, requestURI));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            } else {
                obj = joinPoint.proceed();
            }
        }
        return obj;
    }

    public Map<String, Object> put(Class<?>[] methodParamClazz, String[] params, Object[] args) {
        Map<String, Object> nameValueMap = new HashMap<>();
        for (int i = 0; i < methodParamClazz.length; i++) {
            //排除httpServletRequest的干扰
            if (!methodParamClazz[i].isAssignableFrom(HttpServletRequest.class)) {
                /**
                 * 如果是基本类型以及String类型
                 */
                if (ReflectUtilsType.typeList.contains(methodParamClazz[i].getName())) {
                    if (params != null) {
                        String param = params[i];
                        nameValueMap.put(param, args[i]);
                        put("token", param, "token", AccountUserService.class, "getById", args[i], nameValueMap);
                        put("accountId", param, "oldAccount", AccountService.class, "getByAccountId", args[i], nameValueMap);
                        put("accountUserId", param, "oldAccountUser", AccountUserService.class, "getById", args[i], nameValueMap);
                        put("userId", param, "oldUser", UserService.class, "getByUserId", args[i], nameValueMap);
                        //put("machineId", param, "oldMachine", MachineService.class, "getByMachineId", args[i], nameValueMap);
                    }
                } else {
                    Map<String, Object> fieldMap = null;
                    try {
                        fieldMap = ReflectUtilsType.getNameValues(args[i]);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                    if (StringUtils.isNotNull(fieldMap)) {
                        nameValueMap.putAll(fieldMap);
                    }
                }
            }
        }
        return nameValueMap;
    }

    /**
     * @Description: 通过反射调用方法执行
     * @Param: [key（具体参数名）, param（需要判断的参数名）, mapKey（存入map的key）, objName（实体类名）, methodName（方法名）, args（值集合）, nameValueMap（map）]
     * @return: void
     * @Author: Jing
     * @Date: 2021/4/23
     */
    public void put(String key, String param, String mapKey, Class objName, String methodName, Object args, Map<String, Object> nameValueMap) {
        Object[] objects = {args};
        if (key.equals(param)) {
            Object obj = com.tbit.main.service.manager.util.ReflectUtils.invokeMethodByName(SpringUtils.getBean(objName), methodName, objects);
            if (null != obj) {
                nameValueMap.put(mapKey, obj);
            }
        }
    }


}
